【Android教程】AIDL入门
AIDL,android interface definition language
前言
某天,我问谷歌爸爸,在Android中进程之间如何进行通讯?
我以为这很简单,我去找到要通讯的对象进程,然后建立连接,直接通讯。
然而谷歌爸爸说,不行,你这么做不安全,你崩掉了可能让人家也崩掉,锅不是这么甩的。
我问谷歌爸爸,那怎么办?
谷歌爸爸说,我给你工具,我给你通道,你给我间接地来,我保证你们互不影响。
工具在哪?叫什么名字?
工具在SDK里,有AIDL,Binder,Messenger,Service,需要什么用什么
怎么用?
自己看文档
看不懂怎么办?
边看边写
写了还不懂怎么办?
考虑改行吧
哦
AIDL
在Android中,进程之间是不能直接通讯的,他们也没有公共数据区域,但我们实实在在地需要进程间通讯,怎么办呢?Android为我们提供了四种进程间通讯的方式,正好对应四种基本应用程序组件:Activity, Content Provider, Broadcase, Service.
- Activity可以跨进程调用其他的Activity
- Content Provider以形似DBC(database connector)的方式访问其他应用程序的数据
- Broadcast以发送广播的方式唤起对应进程
- Service以AIDL方式访问其他应用程序的数据
AIDL是一种IPC(Inter-Process Communication)方式,是C/S结构,如果接触过Java中RPC框架(例如hessian),可以很快理解这种方式。
AIDL可以算是一种DSL(Domain Specific Language)。Android中一个进程不允许直接访问另一个进程的内存数据,他们必须要将数据分解(parcel)成操作系统可以识别的最基本的数据类型之后进行传输。AIDL就负责这一块。
与所有技术一样,只有在需要的时候才考虑AIDL,因为跨进程通讯是有代价的。那么什么时候用AIDL呢?
- 如果你只需要IPC,不需要多线程,你应该考虑Android提供的Messenger机制
- 如果你还要多线程,但是不需要多个应用程序并发进行IPC,你应该考虑Android提供的Binder机制(特化的AIDL)
- 如果你需要多个应用程序并发IPC,还需要多线程处理,你应该考虑使用AIDL
那么,AIDL怎么写,怎么用?其实一句话懂的人就懂了:AIDL就是RPC中的服务接口。
如下图示(绿色虚线框为服务端代码,蓝色虚线框为客户端代码):aidl-call
假设我们的应用A需要自己能被别的应用调用和访问,不妨假设调用者为应用B。这样被访问的一方,就是提供服务的一方,即C/S中的S,而访问的一方就是客户端C。
我们需要在应用A中和应用B中都定义AIDL,并且在应用A中实现AIDL接口。
如果A不实现AIDL,是没法以AIDL的方式被调用的。这很好理解,你想跟别人对话,也要看别人愿不愿意跟你对话,对吧。
Android SDK中aidl工具会将.aidl
文件加入一些东西(比如自动加入抽象静态内部类Stub),转换为同名的.java
文件以供使用。
Eclipse和Android Studio都可以调用aidl工具将你定义的.aidl
文件转换成.java
。
AIDL文件中可以引用其他AIDL文件定义的接口,但不能直接引用.java
中定义的接口。
但是!有能力的程序员完全可以自己写.java
文件,完全手工完成接口定义。
案例
继续沿用上一节末尾提到的应用A和应用B,这里为了简化场景,假设A是某个Application中负责远程计算的Service,运行在独立的进程中,B是这同一个Application中负责业务逻辑的模块,需要调用A来进行远程计算。
在具体使用的时候,需要结合具体情境给出回调方式以获取服务实例,这里以使用Service为例,它自带onBind回调,我们只需要实现这个回调返回给客户端一个实例。
当获得了服务端的实例之后就可以直接使用这个对象了。
如下图所示:
由于画图技能没点,该图讲得不好,最好结合自己写的aidl和对应生成的.java
文件来看。
原理
aidl自动生成的文件中,使用抽象类Stub帮助你从外部定义服务端的实现,系统通过Stub和asInterface等存在于该生成文件中的预定义的方法来实现调用。
打开.java
文件,看看它的所有新增的方法和成员,可以帮助深入理解aidl的设计思想和运行原理。它里面有非常神奇的一些用法,例如抽象内部类Stub实现了存在于其外部的接口(它自身!),然后利用Proxy代理使用服务端实现。
客户端获取到的实际上是远程服务的代理。而实际工作,是在服务端的代理中调用方法,以及代理前后调用onTransact进行序列化和反序列化。
以下是示意图,写下去涉及的东西就多了,而且我也没有仔细阅读,这里就不误人子弟了,强烈建议大家自己去阅读感兴趣的部分。
结论
先上最重要的结论:在自带的Binder
机制和Messenger
机制无法解决你的多线程、多应用间通讯的场景时,使用aidl
aidl提供了由系统支持的类RPC的进程间通讯方式。与直接通信的区别在于,系统可以借助aidl的定义来找到实现该aidl的组件,而且看起来好像并没有切换出当前进程环境(事实上是系统帮你把远程服务端运行起来了,aidl的执行都是在服务端,这就是为什么你在使用完毕的时候需要解绑服务端进程以避免内存泄漏),使得客户端使用较为流畅无痕。隔离客户端和服务端,两者只需要面向接口编程,客户端可以完全不关心服务端的行为。
aidl的缺点是传输的数据只支持Java内建基本数据类型(而且short
除外)、String
、CharSequence
、只包含以上支持类型的List和Map,以及实现了Parcelable
(一个类似于Serializable
的接口,但强制要求实现类必须实现反序列化生成器public static final Parcelable.Creator<T> CREATOR
和序列化生成器writeToParcel
,其中CREATOR
虽然是自己写的成员变量,但是名称和修饰词都不能更改)接口的类型。
在List
和Map
这种用到泛型的情况下,需要指明List和Map是作为in/out/inout
哪种方式使用的。网上某视频说在传输数据的时候会有一个打包和解包的过程,而这个过程代价非常大,说是出于节约资源的考虑才要求指明是作为输入还是作为输出。这或许也是考虑之一,但我更倾向于认为是出于保持里氏代换原则的考虑,毕竟这里如果你没声明的话会报编译错误,而一般来说影响性能的行为只会报警告,而不是错误。而且看到in/out
这种词语,我很容易就联想到我的另一篇blogJava中的协变和逆变
另外,如果使用Parcelable
接口传递自定义的数据对象,由于传递对象的时候序列化之后本质上是二进制流,那实现相应的序列化和反序列化的方法时,成员顺序必须完全一致,而且客户端和服务端都需要有完全一致的该自定义类文件。也是由于底层序列化会把short
类型强行提升为int
型,所以就直接不支持short
类型的序列化 。
对于aidl来说,它不直接识别java的源文件,所以需要给出自定义类的相应的aidl描述。当然aidl中对于实体的描述比接口描述要简单得多,只需要直接声明实体就行了。
参考
《Android开发艺术探索》
Android系统进程间通信Binder机制在应用程序框架层的Java接口源代码分析
Android Binder机制
Android跨进程通信的四种方式
Android : 基本空间划分 & IPC框架分析
Android 内核–Binder架构分析
Android AIDL使用详解
Binder与AIDL服务